//
//  TTWebService.m
//  Tint
//
//  Created by Kevin Wu on 2/29/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTWebService.h"

@interface TTWebService (Private)

- (BOOL)shouldUseMultipartPostRequest;

+ (NSThread *)parseThread;
+ (void)parseThreadBody:(id)object;

@end

@implementation TTWebService (Private)


- (BOOL)shouldUseMultipartPostRequest
{
    for ( NSString *name in _parameters ) {
        id value = [_parameters objectForKey:name];
        if ([value isKindOfClass:[NSData class]] || 
            [value isKindOfClass:[UIImage class]])
        {
            return YES;
        }
    }
    return NO;
}


+ (NSThread *)parseThread
{
    static NSThread *sharedParseThread = nil;
    if ( sharedParseThread == nil ) {
        sharedParseThread = [[NSThread alloc] initWithTarget:self 
                                                    selector:@selector(parseThreadBody:) 
                                                      object:nil];
        [sharedParseThread start];
    }
    return sharedParseThread;
}

+ (void)parseThreadBody:(id)object
{
    while ( YES ) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        [[NSRunLoop currentRunLoop] run];
        [pool release];
    }
}

@end







@implementation TTWebService

TTSynthesizeDelegates(_delegates);


#pragma mark - Accessors

@synthesize address = _address;
@synthesize requestMethod = _requestMethod;
@synthesize request = _request;
@synthesize requestTimeoutInterval = _requestTimeoutInterval;
@synthesize requestHeaders = _requestHeaders;
@synthesize requestBody = _requestBody;

@synthesize parsedObject = _parsedObject;
@synthesize cacheKey = _cacheKey;
@synthesize cacheTimeoutInterval = _cacheTimeoutInterval;

@synthesize loading = _loading;
@synthesize cancelled = _cancelled;

@synthesize alreadyInCacheSelector = _alreadyInCacheSelector;
@synthesize didStartSelector = _didStartSelector;
@synthesize didFailSelector = _didFailSelector;
@synthesize willFinishSelector = _willFinishSelector;
@synthesize didFinishSelector = _didFinishSelector;


#pragma mark - Memory

- (id)init
{
    self = [super init];
    if ( self ) {
        [self initialize];
    }
    return self;
}

- (void)dealloc
{
    TTRelease(_address);
    TTRelease(_requestMethod);
    TTRelease(_request);
    _requestTimeoutInterval = 0.0;
    TTRelease(_requestHeaders);
    TTRelease(_requestBody);
    
    TTRelease(_parsedObject);
    TTRelease(_cacheKey);
    _cacheTimeoutInterval = 0.0;
    
    _loading = NO;
    _cancelled = NO;
    
    _alreadyInCacheSelector = NULL;
    _didStartSelector = NULL;
    _didFailSelector = NULL;
    _willFinishSelector = NULL;
    _didFinishSelector = NULL;
    
    TTRelease(_parameters);
    
    [self destroyDelegates];
    
    [super dealloc];
}


#pragma mark - Public

+ (id)sharedObject
{
    return nil;
}

+ (BOOL)hasSharedObject
{
    return NO;
}

+ (void)destroySharedObject
{
}

+ (id)serviceByName:(NSString *)name
{
    NSString *key = [[NSString alloc] initWithFormat:@"%@_%@", [self classString], name];
    id service = [[self serviceMap] objectForKey:key];
    if ( service == nil ) {
        service = [[self alloc] init];
        [(NSMutableDictionary *)[self serviceMap] setObject:service forKey:key];
        TTRelease(service);
        service = [[self serviceMap] objectForKey:key];
    }
    TTRelease(key);
    return service;
}

+ (BOOL)hasServiceForName:(NSString *)name
{
    NSString *key = [[NSString alloc] initWithFormat:@"%@_%@", [self classString], name];
    id service = [[self serviceMap] objectForKey:key];
    TTRelease(key);
    return ( service != nil );
}

+ (void)destroyServiceByName:(NSString *)name
{
    NSString *key = [[NSString alloc] initWithFormat:@"%@_%@", [self classString], name];
    [(NSMutableDictionary *)[self serviceMap] removeObjectForKeyIfNotNil:key];
    TTRelease(key);
}

+ (NSDictionary *)serviceMap
{
    static NSMutableDictionary *serviceMap = nil;
    if ( serviceMap == nil ) {
        serviceMap = [[NSMutableDictionary alloc] init];
    }
    return serviceMap;
}



- (void)initialize
{
    _address = nil;
    _requestMethod = [[NSString alloc] initWithString:@"GET"];
    _request = nil;
    _requestTimeoutInterval = 15.0;
    _requestHeaders = nil;
    _requestBody = nil;
    
    _parsedObject = nil;
    _cacheKey = [[NSString alloc] initWithString:[self classString]];
    _cacheTimeoutInterval = 0.0;
    
    _loading = NO;
    _cancelled = NO;
    
    _alreadyInCacheSelector = NULL;
    _didStartSelector = NULL;
    _didFailSelector = NULL;
    _willFinishSelector = NULL;
    _didFinishSelector = NULL;
    
    _parameters = nil;
}



- (void)setParameters:(NSDictionary *)dictionary
{
    if ( !_loading ) {
        [_parameters removeAllObjects];
        [self addParameters:dictionary];
    }
}

- (void)addParameters:(NSDictionary *)dictionary
{
    if ( !_loading ) {
        for ( NSString *key in [dictionary keyEnumerator] ) {
            NSString *parameter = [dictionary objectForKey:key];
            [self addParameter:parameter forName:key];
        }
    }
}

- (void)addParameter:(NSString *)parameter forName:(NSString *)name
{
    if ( !_loading ) {
        
        if ( _parameters == nil ) {
            _parameters = [[NSMutableDictionary alloc] init];
        }
        
        if ( name ) {
            if ( parameter ) {
                [_parameters setObject:parameter forKey:name];
            } else {
                [_parameters removeObjectForKey:name];
            }
        }
        
    }
}

- (void)removeParameterForName:(NSString *)name
{
    if ( !_loading ) {
        [_parameters removeObjectForKeyIfNotNil:name];
    }
}

- (NSString *)parameterForName:(NSString *)name
{
    return [_parameters objectForKey:name];
}

- (void)clearParameters
{
    if ( !_loading ) {
        [_parameters removeAllObjects];
    }
}

- (void)addPropertiesAsParameter
{
    if ( !_loading ) {
        NSArray *propertyNames = [[self class] newPropertyNames];
        [self addPropertiesInArrayAsParameter:propertyNames exceptProperties:nil];
        TTRelease(propertyNames);
    }
}

- (void)addPropertiesAsParameter:(NSString *)propertyString
{
    if ( !_loading ) {
        [self addPropertiesInArrayAsParameter:[propertyString componentsSeparatedByString:@","] exceptProperties:nil];
    }
}

- (void)addPropertiesAsParameterExcept:(NSString *)exceptPropertyString
{
    if ( !_loading ) {
        
        NSArray *propertyNames = [[self class] newPropertyNames];
        NSArray *exceptProperties = [exceptPropertyString componentsSeparatedByString:@","];
        [self addPropertiesInArrayAsParameter:propertyNames exceptProperties:exceptProperties];
        TTRelease(propertyNames);
    }
}

- (void)addPropertiesInArrayAsParameter:(NSArray *)properties exceptProperties:(NSArray *)exceptProperties
{
    if ( !_loading ) {
        
        
        NSMutableArray *tmpProperties = [[NSMutableArray alloc] init];
        
        for ( NSString *property in properties ) {
            NSString *trimmed = [property newStringByTrimmingWhitespaceAndNewline];
            if ( [trimmed length] > 0 ) {
                [tmpProperties addUnequalObjectIfNotNil:trimmed];
            }
            TTRelease(trimmed);
        }
        
        
        NSMutableArray *tmpExceptProperties = [[NSMutableArray alloc] init];
        
        for ( NSString *property in exceptProperties ) {
            NSString *trimmed = [property newStringByTrimmingWhitespaceAndNewline];
            if ( [trimmed length] > 0 ) {
                [tmpExceptProperties addUnequalObjectIfNotNil:trimmed];
            }
            TTRelease(trimmed);
        }
        
        
        NSMutableArray *finalProperties = [[NSMutableArray alloc] init];
        
        for ( NSString *property in tmpProperties ) {
            if ( ![tmpExceptProperties hasObjectEqualTo:property] ) {
                [finalProperties addUnequalObjectIfNotNil:property];
            }
        }
        
        TTRelease(tmpProperties);
        TTRelease(tmpExceptProperties);
        
        if ( [finalProperties count] > 0 ) {
            
            for ( NSString *name in finalProperties ) {
                NSString *value = [self stringValueForProperty:name];
                [self addParameter:value forName:name];
            }
            
        }
        
        TTRelease(finalProperties);
        
    }
}

- (NSString *)stringValueForProperty:(NSString *)name
{
    NSDictionary *propertyAttributes = [[self class] newPropertyAttributes];
    NSString *attribute = [propertyAttributes objectForKey:name];
    NSString *stringValue = @"";
    if ( attribute ) {
        if ( [attribute hasPrefix:@"Tc"] ) { // BOOL: Tc
            NSNumber *value = [self valueForKey:name];
            if ( value ) {
                if ( [value boolValue] ) stringValue = @"1";
                else stringValue = @"0";
            }
        } else if ( [attribute hasPrefix:@"Ti"] ) { // int: Ti
            NSNumber *value = [self valueForKey:name];
            if ( value ) stringValue = [NSString stringWithFormat:@"%d", [value intValue]];
        } else if ( [attribute hasPrefix:@"Tq"] ) { // long long: Tq
            NSNumber *value = [self valueForKey:name];
            if ( value ) stringValue = [NSString stringWithFormat:@"%lld", [value longLongValue]];
        } else if ( [attribute hasPrefix:@"Td"] ) { // double: Td
            NSNumber *value = [self valueForKey:name];
            if ( value ) stringValue = [NSString stringWithFormat:@"%f", [value doubleValue]];
        } else if ( [attribute hasPrefix:@"T@\"NSString\""] ) { // NSString: T@"NSString"
            NSString *value = [self valueForKey:name];
            if ( value ) stringValue = value;
        } else if ( [attribute hasPrefix:@"T@\"NSDate\""] ) { // NSDate: T@"NSDate"
            NSDate *value = [self valueForKey:name];
            if ( value ) stringValue = [NSString stringWithFormat:@"%f", [value timeIntervalSince1970]];
        }
    }
    TTRelease(propertyAttributes);
    return stringValue;
}






- (void)start
{
    [self refreshWithCacheTimeoutInterval:0];
}

- (void)startWithCacheTimeoutInterval:(NSTimeInterval)timeoutInterval
{
    if ( !_loading ) {
        if ( [[TTDiskCache sharedObject] hasCacheForKey:_cacheKey name:TTWebServiceCacheName] ) {
            [self webServiceAlreadyInCache];
        } else {
            [self refreshWithCacheTimeoutInterval:timeoutInterval];
        }
    }
}

- (void)refreshWithCacheTimeoutInterval:(NSTimeInterval)timeoutInterval
{
    if ( !_loading ) {
        
        _cancelled = NO;
        
        
        [[TTDiskCache sharedObject] removeCacheForKey:_cacheKey name:TTWebServiceCacheName];
        _cacheTimeoutInterval = timeoutInterval;
        
        
        if ( TTIsStringWithText(_address) ) {
            TTRelease(_request);
            TTRelease(_parsedObject);
            
            [self webServiceDidStart];
            
            NSString *address = nil;
            if ( [_requestMethod isEqualToString:@"GET"] ) {
                address = [_address newStringByAddingQueryDictionary:_parameters];
            } else {
                address = [_address copy];
            }
            
            TTDPRINT(@"%@", address);
            _request = [[TTHTTPRequest alloc] initWithAddress:address];
            TTRelease(address);
            
            if ( _requestTimeoutInterval > 0.0 ) {
                _request.timeoutInterval = _requestTimeoutInterval;
            }
            
            _request.requestHeaders = _requestHeaders;
            
            if ( [_requestMethod isEqualToString:@"POST"] ) {
                if ( [_requestBody length] > 0 ) {
                    [_request setRequestBody:_requestBody];
                } else if ( [_parameters count] > 0 ) {
                    if ( [self shouldUseMultipartPostRequest] ) {
                        [_request constructMultipartDataWithParameters:_parameters];
                    } else {
                        NSString *queryString = [_parameters newQueryString];
                        NSData *queryData = [queryString newDataUsingUTF8StringEncoding];
                        [_request setRequestBody:queryData];
                        TTRelease(queryData);
                        TTRelease(queryString);
                    }
                }
            }
            
            _request.requestMethod = _requestMethod;
            
            [_request addDelegate:self];
            [_request startAsynchronous];
        }
        
    }
}

- (void)cancel
{
    _cancelled = YES;
    [_request cancel];
}



- (void)parse:(NSData *)data
{
    TTBeginAutoreleasePool();
    
    id result = nil;
    
    if ( !_cancelled ) {
        result = [self newObjectFromData:data];
    }
    
    
    if ( !(_cancelled) && !TTIsInstance(result, [NSError class]) ) {
        // Save
        TTRetain(result, _parsedObject);
        [self saveResult:result];
        
        if ( _cacheTimeoutInterval > 0.0 ) {
            [[TTDiskCache sharedObject] setData:nil
                                         forKey:_cacheKey
                            withTimeoutInterval:_cacheTimeoutInterval
                                           name:TTWebServiceCacheName];
        }
    }
    
    
    TTRelease(result);
    
    
    if ( _cancelled ) {
        [self webServiceDidFail:[NSError errorWithDescription:@"WebService cancelled"]];
    } else {
        if ( TTIsInstance(result, [NSError class]) ) {
            [self webServiceDidFail:[NSError errorWithDescription:@"WebService parse failed"]];
        } else {
            [self webServiceWillFinish];
            [self webServiceDidFinish];
        }
    }
    
    
    
    TTRelease(_request);
    
    
    TTEndAutoreleasePool();
    
}

- (id)newObjectFromData:(NSData *)data
{
    return nil;
}

- (void)saveResult:(id)result
{
}



- (BOOL)isInCache
{
    return [[TTDiskCache sharedObject] hasCacheForKey:_cacheKey name:TTWebServiceCacheName];
}

- (NSDate *)updateDate
{
    return [[TTDiskCache sharedObject] updateDateOfKey:_cacheKey name:TTWebServiceCacheName];
}



- (void)webServiceAlreadyInCache
{
    [[self delegates] makeObjectsRunSelectorOnMainThread:_alreadyInCacheSelector withObject:self];
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceAlreadyInCache:) withObject:self];
}

- (void)webServiceDidStart
{
    _loading = YES;
    [[self delegates] makeObjectsRunSelectorOnMainThread:_didStartSelector withObject:self];
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceDidStart:) withObject:self];
}

- (void)webServiceDidFail:(NSError *)error
{
    _loading = NO;
    [[self delegates] makeObjectsRunSelectorOnMainThread:_didFailSelector withObject:self];
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceDidFail:withError:) withObject:self withObject:error];
}

- (void)webServiceWillFinish
{
    [[self delegates] makeObjectsRunSelectorOnMainThread:_willFinishSelector withObject:self];
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceWillFinish:) withObject:self];
}

- (void)webServiceDidFinish
{
    _loading = NO;
    [[self delegates] makeObjectsRunSelectorOnMainThread:_didFinishSelector withObject:self];
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceDidFinish:) withObject:self];
}


#pragma mark - TTOperationDelegate

//- (void)operationDidStart:(TTOperation *)operation
//{
//}

//- (void)operationUpdated:(TTOperation *)operation
//{
//}

- (void)operationDidFail:(TTOperation *)operation withError:(NSError *)error
{
    [self webServiceDidFail:[NSError errorWithDescription:@"WebService request failed"]];
}

//- (void)operationWillFinish:(TTOperation *)operation
//{
//}

- (void)operationDidFinish:(TTOperation *)operation
{
    if ( !_cancelled ) {
        [self performSelector:@selector(parse:) 
                     onThread:[[self class] parseThread] 
                   withObject:(_request.responseData) 
                waitUntilDone:NO];
    }
}

@end


